#ifndef __HEAD_cuh__
#define __HEAD_cuh__

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <device_launch_parameters.h>
#include <string>
#include <sstream>
#include <curand.h>
#include <cusolverDn.h>

inline void checkSolver(cusolverStatus_t err, char const * file, unsigned int line){
	if ((err) != CUSOLVER_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUSOLVER error " << err << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		//system("pause");
		//exit(1);
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaRand(curandStatus_t err, char const * file, unsigned int line){
	if ((err) != CURAND_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CURAND error " << err << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		//system("pause");
		//exit(1);
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaError(cudaError_t err, char const * file, unsigned int line){
	if (err != cudaSuccess){
		std::stringstream ss;
		ss << "CUDA error " << err << " " << cudaGetErrorString(err) << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		//system("pause");
		//exit(1);
		throw std::runtime_error(ss.str());
	}
}

inline void check(bool bTrue, const char* msg, char const * file, unsigned int line){
	if (!bTrue){
		std::stringstream ss;
		ss << "Error: \"" << msg << "\" at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		//system("pause");
		//exit(1);
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaStatus(cublasStatus_t status, char const * file, unsigned int line){
	if (status != CUBLAS_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUDA status " << status << " at " << file << ":" << line;
		printf("%s\n", ss.str().c_str());
		//system("pause");
		//exit(1);
		throw std::runtime_error(ss.str());
	}
}

#define CHECK_PARAM(x, msg)   check((x), (msg), __FILE__, __LINE__)
#define CHECK_CUDA(cudaError) checkCudaError((cudaError), __FILE__, __LINE__)
#define CHECK_CUDA_STATUS(cudaStatus) checkCudaStatus((cudaStatus),__FILE__,__LINE__)
#define CHECK_CURAND(err) checkCudaRand((err),__FILE__,__LINE__)
#define CHECK_CUSOLVER(err) checkSolver((err),__FILE__,__LINE__)

//col-major and zero-based
//ld means leading-dimension which is alse the number of rows of matrix in this case
#define IDX2C(row,col,ld) (((col)*(ld))+(row))


//process dataset into 3 matrix: UIview, UIview_test and UUview
#define UIviewFileName "flixster_ratings_20threshold.txt"
#define UUviewFileName "flixster_links_20.txt"
#define nUsers 36492
#define nItems 11927

//#define UIviewFileName "small_ratings.txt"
//#define UUviewFileName "small_links.txt"
//#define nUsers 5014
//#define nItems 5487

#define M_ADD_OPS 1
#define M_MINUS_OPS 2
#define M_TIMES_OPS 3
#define M_DIVIDE_OPS 4

struct Params
{
	float split_rate;
	float maxS;
	float minS;
	int num_of_kmeans;
	int num_of_anchors;
	int r;
	int num_of_LAE_iters;
	int num_of_nearest_anchors;

	float alpha;
	float beta;
	float rho1;
	float rho2;
};

void params_to_string(Params &params, std::string &s);

void write_UIview_data(const float *UIview_test,
	const int m,
	const int n,
	const char * filename);

void read_UIview_and_IUview_data(float *UIview, const int m, const int n, float *IUview,
	const char *filename);

void read_Z_from_file(float *Z, const int d, const int n, const char *filename);
void write_Z_into_file(float *Z, int d, int n, const char *filename);

bool read_data_2_matrix(float *UIview, float *UIview_test, float *UUview,
	const float split_rate, float *IUview);

void get_kernel_config(int sz1, int sz2, dim3& szGrid, dim3& szBlock
	, int& rowPerThread, int& colPerThread);

// device memory, column-majored
int kMeans(float *deviceObjects,      /* in: [numObjs][numCoords] */
	int     numCoords,    /* no. features */
	int     numObjs,      /* no. objects */
	int     numClusters,  /* no. clusters */
	int     maxLoop,      /* maximum number of loops */
	float  *deviceClusters);


bool get_anchors_by_Kmeans(float *devData, float *devCluster,
	int width, int hight,
	int num_of_kmeans, int num_of_anchors);

void quicksort(float *a, int l, int h, bool isDesc = true);
void LAE_process(const float *x, const int nDim,
	const float *U, const int nS,
	float *val_one_line,/* out */
	const int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_iZ_by_LAE(const float *IUview, const float *d_IUview,
	const float* anchors_of_iuview, const float *d_anchors_of_iuview,
	const int nusers, const int nitems, const int nanchors,
	float *iZ,
	const int num_of_nearest_anchors,
	const int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_uZ_by_LAE(const float* UIview, const float* UUview,
	const float *d_UIview, const float *d_UUview,
	const float* anchors_of_uiview, const float* anchors_of_uuview,
	const float *d_anchors_of_uiview, const float *d_anchors_of_uuview,
	const int nusers, const int nitems, const int nanchors,
	float* uZ,
	const int num_of_nearest_anchors,
	const int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_S_from_Z(float *S, const float *Z, const int nObjs, const int nDim,
	cublasHandle_t cublashandle);
void MVDCF(
	float *B,/* out1: r X nusers*/
	float *D,/* out2: r X nitems*/
	const Params &params,
	float *h_S,
	float *h_ST,
	float *h_S_i,
	float *h_S_u);
void write_test_data(const float *UIview_test,
	const int m,
	const int n,
	const char *filename);
void write_into_file(float *B,
	int r,
	int n,
	const char *filename);

#endif